package cn.iocoder.yudao.module.engine.service.core.filter.read;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.StrUtil;
import cn.iocoder.yudao.framework.cache.NettyChannelCacheService;
import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
import cn.iocoder.yudao.module.engine.constant.EngineConstant;
import cn.iocoder.yudao.module.engine.enums.EnumActionType;
import cn.iocoder.yudao.module.engine.enums.EnumInteractionType;
import cn.iocoder.yudao.module.engine.exception.CancelLLMRequestException;
import cn.iocoder.yudao.module.engine.model.aichat.AIChatReq;
import cn.iocoder.yudao.module.engine.model.config.EngineConfig;
import cn.iocoder.yudao.module.engine.model.context.AIMessageContext;
import cn.iocoder.yudao.module.engine.model.aichat.AIChatReplayMessage;
import cn.iocoder.yudao.module.engine.prometheus.EngineWebSocketMessageMetrics;
import cn.iocoder.yudao.module.engine.service.core.queue.audio.AudioQueueManager;
import cn.iocoder.yudao.module.engine.model.engine.EngineOutputData;
import cn.iocoder.yudao.module.engine.model.context.ControlEngineContext;
import cn.iocoder.yudao.module.engine.model.context.EngineSessionContext;
import cn.iocoder.yudao.module.engine.enums.EnumAudioType;
import cn.iocoder.yudao.module.engine.service.core.pipeline.FilterAdapter;
import cn.iocoder.yudao.module.engine.service.core.pipeline.FilterContext;
import cn.iocoder.yudao.module.engine.service.log.IEngineLogService;
import cn.iocoder.yudao.module.engine.util.AudioUtils;
import cn.iocoder.yudao.module.engine.util.EngineConvertUtils;
import cn.iocoder.yudao.module.infra.api.config.IConfigApi;
import cn.iocoder.yudao.module.resource.api.IAudioServiceApi;
import cn.iocoder.yudao.module.resource.api.enums.MessageSourceTypeEnum;
import cn.iocoder.yudao.module.engine.model.aichat.AIChatFuture;
import cn.iocoder.yudao.module.engine.service.core.aichat.IAIChatService;
import cn.iocoder.yudao.module.engine.service.core.aichat.client.ILLMChatClient;
import cn.iocoder.yudao.module.resource.api.IAudioCommandApi;
import cn.iocoder.yudao.module.resource.api.model.AudioCommandDTO;
import cn.iocoder.yudao.module.resource.api.model.AudioDTO;
import lombok.extern.slf4j.Slf4j;
import org.jetbrains.annotations.NotNull;
import org.springframework.stereotype.Component;

import jakarta.annotation.Resource;

import java.time.Duration;
import java.time.Instant;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.Future;
import java.util.concurrent.Semaphore;

/**
 * AI聊天
 */
@Component
@Slf4j
public class AIChatFilter extends FilterAdapter {
    public static final String KEY_BUY_GUIDE_TAGS = "buy_guide_tags";
    @Resource
    private IAIChatService chatService;
    @Resource
    private NettyChannelCacheService channelCacheService;
    @Resource
    private AudioQueueManager audioQueueManager;
    @Resource
    private IAudioCommandApi audioCommandApi;
    @Resource
    private EngineWebSocketMessageMetrics messageMetrics;
    @Resource
    private IEngineLogService logService;
    @Resource
    private IConfigApi configApi;
    @Resource
    private IAudioServiceApi audioApi;
    @Resource
    private EngineConfig engineConfig;

    @Override
    public void read(FilterContext ctx, ControlEngineContext engineContext) throws Exception {
        final String userText = engineContext.getUserText();
        final List<EngineOutputData.SensorInstruction> sensorInstructions = engineContext.getOutputData().getSensorInstructions();
        if (StrUtil.isEmpty(userText) || CollUtil.isNotEmpty(sensorInstructions)) {
            ctx.fireRead(engineContext);
            return;
        }

        if (userText.length() == 1) {
            log.info("AI聊天-字数太短: {}", userText);
            ctx.fireRead(engineContext);
            return;
        }

        final Boolean balanceSufficient = engineContext.getBillingContext().getBalanceSufficient();
        if (!balanceSufficient) {
            log.warn("AI聊天-余额不足, userText = {}", userText);
            logService.log(engineContext, "【余额不足】AI聊天已禁用");
            replyBuyGuide(engineContext);
            ctx.fireRead(engineContext);
            return;
        }

        if (clearAIChatContextFilter(engineContext)) {
            ctx.fireRead(engineContext);
            return;
        }

        aiChat(engineContext, userText);

        ctx.fireRead(engineContext);
    }

    private void replyBuyGuide(ControlEngineContext engineContext) {
        final int buyGuideTriggerCount = engineContext.getSynStatusCtx().getBuyGuideTriggerCount();
        final Integer buyGuideTriggerMaxCount = engineConfig.getBuyGuideTriggerMaxCount();
        if (buyGuideTriggerCount > buyGuideTriggerMaxCount) {
            log.info("AI聊天-购买引导触发次数超限不回复, buyGuideTriggerCount = {}, buyGuideTriggerMaxCount = {}",
                    buyGuideTriggerCount, buyGuideTriggerMaxCount);
            logService.log(engineContext, "【余额不足】购买引导触发次数超限不回复");
            return;
        }

        final String tagStr = configApi.getValueByKey(KEY_BUY_GUIDE_TAGS);
        if (StrUtil.isEmpty(tagStr)) {
            return;
        }

        final String[] tags = tagStr.split("\\|");
        if (tags == null || tags.length == 0) {
            return;
        }

        final Integer roleId = engineContext.getRoleId();
        final List<AudioDTO> audios = audioApi.queryAudioByTag(roleId, Arrays.asList(tags));
        log.info("查询购买引导音频, audios.size = {}", audios.size());
        if (CollUtil.isEmpty(audios)) {
            return;
        }

        final AudioDTO audio = AudioUtils.randomAudio(engineContext.getSessionCtx(), audios);
        final EngineOutputData.Audio guideAudio = EngineConvertUtils.toEngineOutputData(
                null, IdUtil.objectId(), MessageSourceTypeEnum.SYSTEM, EnumAudioType.BUY_GUID, audio
        );
        log.info("随机购买引导音频, guideAudio = {}", JsonUtils.toJsonString(guideAudio));

        audioQueueManager.addAndSendAudioNotSave(engineContext.getSessionCtx(), guideAudio);
        engineContext.getSynStatusCtx().addBuyGuideTriggerCount();
    }

    private void aiChat(ControlEngineContext engineContext, String userText) {
        final String ttsName = engineContext.getInputData().getTtsName();
        final AIChatReq req = AIChatReq.builder()
                .chatId(engineContext.getChatId())
                .chatClient(new AILLMChatClient(engineContext))
                .websocketStart(engineContext.getWebsocketStart())
                .userId(engineContext.getUserId())
                .gameMatchId(engineContext.getGameMatchId())
                .wsSessionId(engineContext.getSessionCtx().getWsSessionId())
                .role(engineContext.getSessionCtx().getRole())
                .tone(engineContext.getSessionCtx().getTone())
                .userText(userText)
                .historyMessages(engineContext.getSessionCtx().getHistoryMessageQueue().getMessageList())
                .ttsName(ttsName)
                .build();
        final AIChatFuture aiChatFuture = chatService.createMessage(req);

        final List<AIChatFuture> removedFutures = engineContext.getAiChatQueue().add(aiChatFuture);
        log.info("AI聊天-添加AIChat, chatId = {}, queue.size = {}, removedFutures.size = {}, userText = {}",
                engineContext.getChatId(), engineContext.getAiChatQueue().size(), removedFutures.size(), userText);
        cancelFutures(engineContext, removedFutures);
    }

    private boolean clearAIChatContextFilter(ControlEngineContext engineContext) {
        final String userText = engineContext.getUserText();
        final AudioCommandDTO cmd = audioCommandApi.getAudioCommandByOperateType("Phone", "VolumeClose");
        if (cmd != null && StrUtil.isNotEmpty(cmd.getKeyword())) {
            final String[] words = cmd.getKeyword().split("\\|");
            for (String word : words) {
                if (userText.contains(word)) {
                    cancelFutures(engineContext, engineContext.getSessionCtx().getAiChatQueue().getQueue());
                    audioQueueManager.clear(engineContext);
                    log.warn("AI聊天-执行清空操作, word = {}, keyword = {}, userText = {}",
                            word, cmd.getKeyword(), userText);
                    return true;
                }
            }
        }
        return false;
    }

    private static void cancelFutures(ControlEngineContext engineContext,
                                      List<AIChatFuture> removedFutures) {
        if (CollUtil.isEmpty(removedFutures)) {
            return;
        }

        for (AIChatFuture removedFuture : removedFutures) {
            if (removedFuture.isCancelled()) {
                continue;
            }

            removedFuture.setCancelled(true);
            log.info("AI聊天-取消AIChat, chatId = {}, queue.size = {}, userText = {}",
                    removedFuture.getChatId(), engineContext.getAiChatQueue().size(), removedFuture.getUserText());

            if (removedFuture.getLlmFuture() != null) {
                log.info("AI聊天-取消AIChat-LLM, chatId = {}, userText = {}, future = {}",
                        removedFuture.getChatId(), removedFuture.getUserText(), removedFuture.getLlmFuture());
                removedFuture.getLlmFuture().cancel(true);
            }

            final List<Future<?>> ttsFutures = removedFuture.getTtsFuturesWithCopy();
            if (CollUtil.isNotEmpty(ttsFutures)) {
                for (Future<?> ttsFuture : ttsFutures) {
                    log.info("AI聊天-取消AIChat-TTS, chatId = {}, userText = {}, future = {}",
                            removedFuture.getChatId(), removedFuture.getUserText(), ttsFuture);
                    ttsFuture.cancel(true);
                }
                removedFuture.clearTtsFutures();
            }
        }
    }

    public class AILLMChatClient implements ILLMChatClient {
        private final ControlEngineContext engineCtx;
        private final EngineSessionContext sessionCtx;

        public AILLMChatClient(ControlEngineContext engineCtx) {
            this.engineCtx = engineCtx;
            this.sessionCtx = engineCtx.getSessionCtx();
        }

        @Override
        public Semaphore getSemaphore(EnumActionType actionType) {
            switch (actionType) {
                case LLM:
                    return sessionCtx.getLlmSemaphore();
                case TTS:
                    return sessionCtx.getTtsSemaphore();
                default:
                    throw new IllegalArgumentException("不支持的操作类型: " + actionType);
            }
        }

        @Override
        public void send(AIMessageContext messageCtx, AIChatReplayMessage msg) {
            final String audioUrl = msg.getAudioData() != null ? msg.getAudioData().getUrl() : null;
            if (StrUtil.isEmpty(audioUrl)) {
                return;
            }

            if (!channelCacheService.isOnline(sessionCtx.getWsSessionId())) {
                log.info("AI聊天-用户已离线, userId = {}, wsSessionId = {}",
                        sessionCtx.getUserId(), sessionCtx.getWsSessionId());
                return;
            }

            final EngineOutputData.Audio audio = createOutputAudio(messageCtx, msg, audioUrl);
            audioQueueManager.addAndSendAudio(engineCtx, messageCtx, audio);

            messageMetrics.recordSuccessRequestDuration(
                    EnumInteractionType.CHAT, EngineConstant.ENGINE_INPUT_MAPPING,
                    messageCtx.getAudioReplyCount().get(),
                    Duration.between(messageCtx.getWebsocketStart(), Instant.now())
            );
        }

        @Override
        public void complete(AIMessageContext messageCtx) {
            sessionCtx.getAiChatQueue().remove(messageCtx.getAiChatFuture());
            log.info("AI聊天-完成-AI成功, chatId = {}, queue.size = {}, userText = {}",
                    messageCtx.getChatId(), engineCtx.getAiChatQueue().size(), messageCtx.getUserText());
            messageMetrics.recordSuccessRequest(EnumInteractionType.CHAT, EngineConstant.ENGINE_INPUT_MAPPING);
        }

        @Override
        public void completeWithError(AIMessageContext messageCtx, Throwable ex) {
            sessionCtx.getAiChatQueue().remove(messageCtx.getAiChatFuture());
            if (ex instanceof CancelLLMRequestException) {
                log.info("AI聊天-完成-AI取消, chatId = {}, queue.size = {}, userText = {}, msg = {}",
                        messageCtx.getChatId(), engineCtx.getAiChatQueue().size(), messageCtx.getUserText(), ex.getMessage());
                messageMetrics.recordCancelRequest(EnumInteractionType.CHAT, EngineConstant.ENGINE_INPUT_MAPPING);
            } else {
                cancelFutures(engineCtx, Arrays.asList(messageCtx.getAiChatFuture()));
                log.warn("AI聊天-完成-AI出错, chatId = {}, queue.size = {}, userText = {}, msg = {}",
                        messageCtx.getChatId(), engineCtx.getAiChatQueue().size(), messageCtx.getUserText(), ex.getMessage());
                messageMetrics.recordExceptionRequest(EnumInteractionType.CHAT, EngineConstant.ENGINE_INPUT_MAPPING);
            }
        }

        @NotNull
        private EngineOutputData.Audio createOutputAudio(AIMessageContext messageCtx, AIChatReplayMessage msg, String audioUrl) {
            final EngineOutputData.Audio audio = new EngineOutputData.Audio();
            audio.setChatId(engineCtx.getChatId());
            audio.setMessageId(messageCtx.getMessageId());
            if (messageCtx.isFromLLMCache()) {
                audio.setMessageSendType(MessageSourceTypeEnum.VECTOR);
            } else {
                audio.setMessageSendType(MessageSourceTypeEnum.AI);
            }
            audio.setAudioType(EnumAudioType.CHAT);
            audio.setAudioId(msg.getAudioData().getId());
            audio.setAudioDesc(msg.getAudioData().getSentence());
            audio.setAudioUrl(audioUrl);
            audio.setRelationType(1);
            audio.setFromMessage(engineCtx.getUserText());
            return audio;
        }
    }

}
